// #ifdef H5 || WEB || APP || APP-PLUS
import OSS from 'ali-oss'; // H5/App平台使用
// 引入文件压缩库
import Compressor from 'compressorjs'; 
// #endif
// #ifdef MP
// import crypto from "crypto-js";
// import { Base64 } from "js-base64";
// #endif
// 引入获取STS Token的API
import { getStsToken as getSts } from '@/apis/system.js';
// 引入检查传入的URL是否为常见的图片链接
import { isImageLink, isImageFile } from '@/utils/utils';
import { getImageInfo,getPathInfo, removePath } from '@/utils/watermarkTool.js';
const base64 = require('@/utils/base64.js');

import md5 from 'js-md5';
/**
 * OssService类提供了与阿里云OSS进行文件上传交互的功能。
 * 它使用STS Token进行安全认证，并支持小程序和Web端的文件上传。
 */
export default class OssService {
	/**
   * 构造函数初始化OSS服务选项和STS Token状态。
   * @param {Object} options OSS服务的配置选项，包括区域(region)和存储桶(bucket)等信息。
   */
	constructor(options) {
		this.options = options;
		this.stsToken = null; // 用于STS Token的缓存
		this.stsTokenExpire = 0; // 用于记录STS Token的过期时间(现在已设置一个小时过期)
		this._lock = false; // getStsToken 的锁
		this._pendingRequest = null; // getStsToken的pendingRequest
		this.imageMaxSize = 1024 * 1024 * 10;// 默认图片最大为10M
		this.imageQuality = 100;// 默认图片质量为80
	}

	/**
   * 异步方法获取STS Token。
   * 如果当前STS Token有效，则直接返回缓存的Token；否则，通过API请求获取新的Token。
   * @returns {Promise<Object>} 返回STS Token对象。
   */
	async getStsToken() {
		if (this._pendingRequest || this._lock) {
			return this._pendingRequest;
		}
		if (this._pendingRequest) {
			await this._pendingRequest;
		}
		// 设置锁，防止新请求
		this._lock = true;
		try {
			this._pendingRequest = (async () => {
				try {
					const res = await getSts();
					if (res && res.data) {
						this.stsToken = res.data;
						const date = new Date();
						date.setSeconds(date.getSeconds() + 15);
						// this.stsTokenExpire = new Date(this.stsToken.expiration).getTime(); // 更新过期时间
						this.stsTokenExpire = date.getTime(); // 更新过期时间
						return this.stsToken;
					} else {
						throw new Error('获取STS Token失败：返回结果格式不符合预期');
					}
				} catch (error) {
					throw error;
				}
			})();

			// 等待pendingRequest完成
			const result = await this._pendingRequest;

			// 返回结果
			return result;
		} finally {
			// 清除锁
			this._lock = false;

			// 只有当_pendingRequest已经完成或失败时才重置它
			this._pendingRequest = null;
		}
	}
	/**
	 * 获取文件的基本信息，特别是文件的大小
	 * @param {string} filePath - 文件的路径
	 * @returns {Promise<number>} 返回一个Promise对象，该对象在解析时包含文件的大小（以字节为单位）
	 */
	getFileInfo(filePath) {
		// 返回一个Promise对象，用于处理异步操作
		return new Promise((resolve, reject) => {
			// 获取文件系统管理器实例
			const fs = uni.getFileSystemManager();
			// 使用文件系统管理器获取文件信息
			fs.getFileInfo({
				filePath: filePath, // 文件的路径
				success(res) {
					// 如果成功获取文件信息，则将结果解析为Promise的resolve值
					resolve(res);
				},
				fail(err) {
					// 如果获取文件信息失败，则将错误作为Promise的reject值
					reject(err);
				}
			});
		});
	}
	/**
	 * 获取图片信息-uni.getImageInfo
	 * @param {string} filePath - 图片文件的路径
	 * @returns {Promise} 返回一个Promise对象，用于处理异步压缩操作
	 */
	getImageInfox(filePath) {
		// 返回一个Promise对象，用于处理异步操作
		return new Promise((resolve, reject) => {
			// 获取文件扩展名
			// 调用uni.compressImage方法进行图片压缩
			uni.getImageInfo({
				src: filePath, // 文件的路径
				success(res) {
					// 如果成功返回压缩后的文件，则将结果解析为Promise的resolve值
					resolve(res);
				},
				fail(err) {
					// 如果压缩失败，则将错误作为Promise的reject值
					reject(err);
				}
			});
		});
	}
	/**
	 * 压缩图片-uni.compressImage
	 * @param {string} filePath - 图片文件的路径
	 * @param {Object} compress - 可选的压缩选项，默认为{}
	 * @returns {Promise} 返回一个Promise对象，用于处理异步压缩操作
	 */
	compressImageByUni(filePath, compress = {}) {
		// 返回一个Promise对象，用于处理异步操作
		return new Promise(async (resolve, reject) => {
			// 获取文件扩展名
			let extension = filePath.split('.')[1];
			console.log(extension, 'extension');
			// 如果compress对象为空且文件扩展名为'jpg'或'jpeg'，则设置压缩质量
			if (Object.keys(compress).length === 0) {
				compress.quality = this.imageQuality;
			}
			// 如果压缩质量不是数字，则设置默认值
			if (compress.quality && isNaN(Number(compress.quality))) {
				compress.quality = this.imageQuality;
			} else if (compress.quality && typeof compress.quality === 'number') {
				compress.quality = Number(compress.quality);
			}
			// 如果文件扩展名不是'jpg'或'jpeg'，则设置压缩宽度
			// if (extension !== 'jpg' && extension !== 'jpeg') {
			// 	const imageInfo = await this.getImageInfo(filePath);
			// 	console.log(imageInfo,'imageInfo');
			// 	compress.compressedWidth = Math.round(imageInfo.width * compress.quality / 100);
			// }
			console.log(compress, 'compress');
			// 调用uni.compressImage方法进行图片压缩
			uni.compressImage({
				src: filePath, // 文件的路径
				...compress, // 传入压缩选项
				success(res) {
					// 如果成功返回压缩后的文件，则将结果解析为Promise的resolve值
					resolve(res.tempFilePath);
				},
				fail(err) {
					// 如果压缩失败，则将错误作为Promise的reject值
					reject(err);
				}
			});
		});
	}
	/**
	 * 压缩图片-封装了Compressor.js库
	 * @param {File} file - 图片文件
	 * @param {Object} compress - 可选的压缩选项，默认为{}
	 * @returns {Promise} 返回一个Promise对象，用于处理异步压缩操作
	 */
	compressImageByCompressor(file, compress = {}) {
		// 返回一个Promise对象，用于处理异步操作
		return new Promise((resolve, reject) => {
			if (compress.compressedWidth) {
				compress.width = compress.compressedWidth;
			}
			if (compress.compressedHeight) {
				compress.heigth = compress.compressedHeight;
			}
			new Compressor(file, {
				quality: this.imageQuality / 100,
				...compress,
				// The compression process is asynchronous,
				// which means you have to access the `result` in the `success` hook function.
				success(result) {
					resolve(result);
				},
				error(err) {
					console.log(err.message);
					reject(err);
				},
			});
		});
	}
	/**
   * 设置上传文件的STS策略。
   * 使用STS（Security Token Service）令牌来配置一个具有时间限制的上传策略，以确保文件上传过程的安全性。
   * 这个方法生成一个包含过期时间和大小限制的策略文本，然后将其编码为Base64格式，用于后续的文件上传操作。
   *
   * @param stsToken 包含STS令牌相关信息的对象，用于生成上传策略。
   *                 必须包含accessKeyId、accessKeySecret和securityToken字段。
   * @returns 返回一个包含上传策略的formData对象，可以用于前端的文件上传操作。
   */
	setStsPolicy(stsToken) {
		// 获取当前日期时间，用于设置策略的过期时间
		const date = new Date();
		// 设置策略过期时间为当前时间加1小时
		// 设置一小时过期时间
		date.setHours(date.getHours() + 1);
		// 构建上传策略文本，包括过期时间和大小限制条件
		const policyText = {
			expiration: date.toISOString(), // 设置policy过期时间。
			conditions: [
				// 限制上传文件大小在0到1GB之间
				// 限制上传大小。
				['content-length-range', 0, 1024 * 1024 * 1024],
			],
		};

		// 将策略文本转换为JSON格式，并进行Base64编码
		const policy = Base64.encode(JSON.stringify(policyText)); // policy必须为base64的string。

		// 使用accessKeySecret对编码后的策略进行签名，确保策略的安全性
		const signature = this.computeSignature(stsToken.accessKeySecret, policy);

		// 构建上传所需的formData对象，包含STS令牌信息和签名策略
		const formData = {
			...stsToken,
			OSSAccessKeyId: stsToken.accessKeyId,
			signature,
			policy,
			'x-oss-security-token': stsToken.securityToken || stsToken.stsToken,
			dateExpires: date,

		};

		// 返回构建好的formData对象
		return formData;
	}
	/**
   * 异步方法上传文件到OSS。
   * @param {string} file 要上传的文件对象。
   * @param {string} keyPrefix 文件在OSS中的前缀。
   * @param {string} fileName 文件名称。
   * @param {string} contentType 文件的Content-Type。
   * @param {Object} meta 文件的元数据。
   * @param {Object} headers 请求头对象，用于自定义请求头。
   * @param {Object} callback 设置上传成功回调参数，小程序上传暂不支持。
   * @param {Function} onProgressUpdate 上传进度回调函数。
   * @param {string} urlParams URL携带的参数。
   * @param {Object} options 其他可选参数。
   * @returns {Promise} 上传操作的Promise对象。
   */
	async uploadFile({
		tempFilePath,
		keyPrefix = '',
		fileName = '',
		contentType = '',
		meta = {},
		headers = {},
		callback = {},
		onProgressUpdate = () => { },
		urlParams = '',
		...options
	}) {
		if (!tempFilePath) {
			throw new Error('文件参数不能为空');
		}
		console.log(fileName, 'fileName');
		let imageMaxSize = this.imageMaxSize;
		if (options && options.imageMaxSize && options.imageMaxSize > 0) {
			imageMaxSize = options.imageMaxSize;
		}
		console.log(tempFilePath, 'uploadFile-uploadFile');
		const { uniPlatform } = uni.getSystemInfoSync();
		let result;
		let info;
		// let result = await this.uploadFileForMiniProgram(file, keyPrefix);
		// return result;
		switch (uniPlatform) {
		case 'mp-weixin':
		case 'mp-qq':
		case 'mp-baidu':
		case 'mp-alipay':
			console.log('小程序平台上传');
			info = await this.getFileInfo(tempFilePath);
			console.log(info, 'old-info');
			if (isImageLink(tempFilePath) && imageMaxSize && info.size > imageMaxSize) {
				// 图片文件过大，压缩图片
				tempFilePath = await this.compressImageByUni(tempFilePath, options.compress || {});
			}
			// 小程序平台上传逻辑
			result = await this.uploadFileForMiniProgram({
				tempFilePath,
				keyPrefix,
				fileName,
				contentType,
				meta,
				headers,
				callback,
				onProgressUpdate,
				urlParams,
				...options
			});
			break;
		case 'h5':
		case 'web':
		case 'app':
		case 'app-plus':
			// H5/App平台上传逻辑
			result = await this.uploadFileForWeb({
				tempFilePath,
				keyPrefix,
				fileName,
				contentType,
				meta,
				headers,
				callback,
				onProgressUpdate,
				urlParams,
				...options
			});
			break;
		default:
			throw new Error('Unsupported platform');
		}

		return result;
	}

	/**
   * 专为小程序设计的文件上传方法，使用STS Token进行认证。
   * @param {string} tempFilePath 小程序中的临时文件链接。
   * @param {string} keyPrefix 文件在OSS中的前缀。
   * @param {string} fileName 文件名称。
   * @param {string} contentType 文件的Content-Type。
   * @param {Object} meta 文件的元数据。
   * @param {Object} headers 请求头对象，用于自定义请求头。
   * @param {Object} callback 设置上传成功回调参数，小程序上传暂不支持。
   * @param {Function} onProgressUpdate 上传进度回调函数。
   * @param {string} urlParams URL携带的参数。
   * @param {Object} options 其他可选参数。
   * @returns {Promise} 上传操作的Promise对象。
   */
	async uploadFileForMiniProgram({
		tempFilePath,
		keyPrefix = '',
		fileName,
		contentType = '',
		meta = {},
		headers = {},
		callback = {},
		onProgressUpdate = () => { },
		urlParams = '',
		...options
	}) {
		// 逻辑不变，只是调用时会先通过getStsToken方法自动处理STS Token的获取和缓存
		return this.uploadWithStsToken({
			tempFilePath,
			keyPrefix,
			isMiniProgram: true,
			fileName,
			contentType,
			meta,
			headers,
			callback,
			onProgressUpdate,
			urlParams,
			...options
		});
	}

	/**
   * 专为Web端设计的文件上传方法，使用STS Token进行认证。
   * @param {string} tempFilePath 要上传的临时文件链接。
   * @param {string} keyPrefix 文件在OSS中的前缀。
   * @param {string} fileName 文件名称。
   * @param {string} contentType 文件的Content-Type。
   * @param {Object} meta 文件的元数据。
   * @param {Object} headers 请求头对象，用于自定义请求头。
   * @param {Object} callback 设置上传成功回调参数，小程序上传暂不支持。
   * @param {Function} onProgressUpdate 上传进度回调函数。
   * @param {string} urlParams URL携带的参数。
   * @param {Object} options 其他可选参数。
   * @returns {Promise} 上传操作的Promise对象。
   */
	async uploadFileForWeb({
		tempFilePath,
		keyPrefix = '',
		fileName,
		contentType = '',
		meta = {},
		headers = {},
		callback = {},
		onProgressUpdate = () => { },
		urlParams = '',
		...options
	}) {
		// 逻辑不变，也是调用uploadWithStsToken方法
		return this.uploadWithStsToken({
			tempFilePath,
			keyPrefix,
			isMiniProgram: false,
			fileName,
			contentType,
			meta,
			headers,
			callback,
			onProgressUpdate,
			urlParams,
			...options
		});
	}

	/**
   * 根据是否为小程序上传，使用STS Token进行文件上传操作。
   * @param {string} tempFilePath 要上传的临时文件链接。
   * @param {string} keyPrefix 文件在OSS中的前缀。
   * @param {boolean} isMiniProgram 标志位，表示是否为小程序上传。
   * @param {string} fileName 文件名称。
   * @param {string} contentType 文件的Content-Type。
   * @param {Object} meta 文件的元数据。
   * @param {Object} headers 请求头对象，用于自定义请求头。
   * @param {Object} callback 设置上传成功回调参数，小程序上传暂不支持。
   * @param {Function} onProgressUpdate 上传进度回调函数。
   * @param {string} urlParams URL携带的参数。
   * @param {Object} options 其他可选参数。
   * @returns {Promise} 上传操作的Promise对象。
   */
	async uploadWithStsToken({
		tempFilePath,
		keyPrefix = '',
		fileName,
		isMiniProgram = false,
		contentType = '',
		meta = {},
		headers = {},
		callback = {},
		onProgressUpdate = () => { },
		urlParams = '',
		...options
	}) {
		let stsToken;
		// 检查STS Token是否有效，如果有效直接返回缓存的Token
		if (this.stsToken && Date.now() < this.stsTokenExpire) {
			stsToken = this.stsToken;
		} else {
			this.stsToken = await this.getStsToken();
			stsToken = this.stsToken;
		}
		// const stsToken = this.stsToken;
		// mock数据
		// const stsToken = {
		//   region: "oss-cn-shenzhen",
		//   accessKeyId: "STS.NTLGXnf5ASKLYxqSq3ZJbzUXp",
		//   accessKeySecret: "Bn9xgst5desB9u1DgnwVzJas1qtbEUpf9vcKHnkXG6se",
		//   stsToken:
		//     "CAISogN1q6Ft5B2yfSjIr5f5DOLai+pg5ImnW17At3FmVsVOlZDzkjz2IHBKdXRuB+oYv/U0n2tQ6/8dlqFpTJtIY0XBYJPFY3GyHlnzDbDasumZsJba4f/MQBpLYnyW9cvWZPqDobq/Ht6md1HDkAJq3LL+bk/Mdle5MJqP+5sFC9MMRVuAcCZhDtVbLRcA0MgBLinpKOqKOBzniXb5B04Kxg1nkjFH9LiYyM+R4QbihVL1y+kaxfv2OYP2V9RwG519Xtyp4uFsbavG1gdb73o587xt3pRZ5RDNvNyWH2dc+Q6HNOzo+9JgLQl2HN9YfZRJt//hj/Z1l/XOnoDssXZ3MPpSTj7USfLLoqKGOpvwZ4xoKe2mZy6cj4/QbsauiW5+PyJHbjEtUsE6N3p9BSYrTjznMaK98DjIGFzzFfnYjv9sj8sklA6zpYfTeELkWKmCwb+yxHyv3p1D3Xc2qo+BGsduG2QkG9i3tG9UnjBTjEMPjny49lAVNM2tgiGnUxEaw5ey/kQ7TOG7OzditG55QTA7w5canyQbO8Ze44IN3SU7Kd4+GoABGAtxEAq+zE3IyrVSCLdwdjeXDdnmLC8R++4Q5QhfWKqtvFGTE7uk2LQ/KhMsCYxBDAfqxMU5L/3LJklDlWML2XcaQzb1D+3dlAzHpWFk2f9IddsbbZDSBUsbZ/3YLuk11bTIuQF2kAIbRFo7Yi/8Yv4ydOUzgkFPbywB8LArotggAA==",
		//   bucket: "erised-fresh",
		//   expiration: "2024-07-02T04:10:55Z",
		//   securityToken:
		//     "CAISogN1q6Ft5B2yfSjIr5f5DOLai+pg5ImnW17At3FmVsVOlZDzkjz2IHBKdXRuB+oYv/U0n2tQ6/8dlqFpTJtIY0XBYJPFY3GyHlnzDbDasumZsJba4f/MQBpLYnyW9cvWZPqDobq/Ht6md1HDkAJq3LL+bk/Mdle5MJqP+5sFC9MMRVuAcCZhDtVbLRcA0MgBLinpKOqKOBzniXb5B04Kxg1nkjFH9LiYyM+R4QbihVL1y+kaxfv2OYP2V9RwG519Xtyp4uFsbavG1gdb73o587xt3pRZ5RDNvNyWH2dc+Q6HNOzo+9JgLQl2HN9YfZRJt//hj/Z1l/XOnoDssXZ3MPpSTj7USfLLoqKGOpvwZ4xoKe2mZy6cj4/QbsauiW5+PyJHbjEtUsE6N3p9BSYrTjznMaK98DjIGFzzFfnYjv9sj8sklA6zpYfTeELkWKmCwb+yxHyv3p1D3Xc2qo+BGsduG2QkG9i3tG9UnjBTjEMPjny49lAVNM2tgiGnUxEaw5ey/kQ7TOG7OzditG55QTA7w5canyQbO8Ze44IN3SU7Kd4+GoABGAtxEAq+zE3IyrVSCLdwdjeXDdnmLC8R++4Q5QhfWKqtvFGTE7uk2LQ/KhMsCYxBDAfqxMU5L/3LJklDlWML2XcaQzb1D+3dlAzHpWFk2f9IddsbbZDSBUsbZ/3YLuk11bTIuQF2kAIbRFo7Yi/8Yv4ydOUzgkFPbywB8LArotggAA==",
		//   tenantId: "592800",
		// };
		// 路径默认为租户路径
		if (!keyPrefix) {
			keyPrefix = `${stsToken.tenantId}/`;
		} else {
			keyPrefix = `${stsToken.tenantId}/${keyPrefix}`;
		}
		// console.log(stsToken, "stsToken ----1");
		// console.log(tempFilePath, "uploadWithStsToken-tempFilePath ----1");
		try {
			if (isMiniProgram) {
				// this.stsToken = this.setStsPolicy(stsToken);
				// console.log(this.stsToken, "stsToken-setStsPolicy");
				// this.stsTokenExpire = Date.now() + res.data.Expires * 1000; // 更新过期时间
				return this.uploadFileWx({
					tempFilePath,
					keyPrefix,
					stsToken,
					fileName,
					contentType,
					meta,
					headers,
					onProgressUpdate,
					urlParams,
					...options
				});
			} else {
				// console.log(stsToken, "stsToken");
				// 获取当前日期时间，用于设置策略的过期时间
				// const date = new Date();
				// // 设置策略过期时间为当前时间加1小时
				// // 设置一小时过期时间
				// date.setHours(date.getHours() + 1);
				// this.stsTokenExpire = date.getTime(); // 更新过期时间
				// 初始化OSS 
				
				const md5Path = md5(tempFilePath);

				const client = new OSS({
					region: stsToken.region,
					bucket: stsToken.bucket,
					accessKeyId: stsToken.accessKeyId,
					accessKeySecret: stsToken.accessKeySecret,
					stsToken: stsToken.stsToken,
					// 可选，默认为true，表示使用HTTPS协议
					secure: true, 
					refreshSTSToken: async (client) => {
						try {
							const newSTSToken = await  this.getStsToken();
							client.options.accessKeyId = newSTSToken.data.accessKeyId;
							client.options.accessKeySecret = newSTSToken.data.accessKeySecret;
							client.options.stsToken = newSTSToken.data.stsToken;
							console.log('STS 令牌已刷新');
						} catch (error) {
							console.error('刷新 STS 令牌时出错:', error);
						}
					}
				});
				// 设置策略
				const host = `https://${stsToken.bucket}.${stsToken.region}.aliyuncs.com/`;
				
				const pathInfo = await getPathInfo(md5Path);

				// 从 window.isIOS or window.isAndroid 中获取值
				const isIOS = window?.isIOS ?? false;
				const isAndroid = window?.isAndroid ?? false;
				
				// 根据 isIOS or isAndroid 设置 source 值
				const source = isIOS ? '1' : isAndroid ? '2' : '0';

				const replaceMeta = {...meta, source };

				// 判断 tempFilePath 是否 base64
				const isBase64 = tempFilePath.startsWith('data:image');
				
				// 哪果是 base64,判断图片格式
				let extension = '';
				if (isBase64) {
					// 从base64中提取 extension
					extension = tempFilePath.split(';')[0].split('/')[1];
				} else {
					// 从 tempFilePath 抽取文件扩展名
					extension = tempFilePath.split('.').pop()?.toLowerCase() || '';
				}
				
				let key = keyPrefix + (fileName ? `${fileName}.${extension}` : tempFilePath.split('/').at(-1));

				const callbackPayload = { ...callback };

				let resultUrl = `${host}${key}${urlParams}`;
				
				if (pathInfo && pathInfo.imgId) {
					// const { imgId = '' } = pathInfo;	

					let imgId = pathInfo.imgId || '';
				
					let imghead = await getImageInfo(imgId);

					if (!imghead || Object.keys(imghead).length == 0) {
						let urlInfo = await getPathInfo(tempFilePath);
						if (urlInfo.imgId) {
							fileName = urlInfo.imgId;
							imghead = urlInfo;
							imgId = urlInfo.imgId;
						}
					}
					
					Object.assign(replaceMeta, { ...imghead, imgId });

					// #ifdef MP-WEIXIN
					replaceMeta.source = 3;
					// #endif

					if (tempFilePath.indexOf('mrm_tmp') !== -1) {
						// base64 中提取 extension
						fileName = `${imgId}.${extension}`;
						key = keyPrefix + fileName; 
					} 

					resultUrl = `${host}${key}${urlParams}`;

					const customValue = {
						...replaceMeta,
						url: resultUrl
					};

					if (customValue.result !== undefined && customValue.result !== '') {
						Object.assign(callbackPayload,{
							body: `{${Object.entries(customValue).map(([key]) => {
								const str = `${key}:'\${x:${key}}'`;
								return str;
							}).join(',')}}`,
							customValue
						});
	
						Object.assign(callbackPayload, {
							url: stsToken.callBackUrl,
							contentType: 'application/x-www-form-urlencoded', 
							body: callbackPayload.body || '',
							customValue: callbackPayload.customValue || {}
						});
						console.log(JSON.stringify(callbackPayload), 'replaceCallback');
					}
				}

				const metaObj = {
					...Object.fromEntries(
						Object.entries({ ...replaceMeta, url: resultUrl }).map(
							([key, value]) => {
								console.log(key, value, '[metaObj] key, value');
								return [key.replace(/_/g, '-'), encodeURIComponent(value)];
							}
						)
					)
				};

				console.log('[metaObj]:',JSON.stringify(metaObj));

				// 删除临时文件
				removePath(md5Path);

				return this.uploadFileByOSS({
					client,
					tempFilePath,
					keyPrefix,
					host,
					fileName,
					contentType,
					meta: metaObj,
					headers,
					callback: callbackPayload,
					onProgressUpdate,
					urlParams,
					...options
				});
			}
		} catch (err) {
			console.error('Upload failed:', err);
			throw err;
		}
	}
	/**
   * 通过OSS（Object Storage Service）上传文件。
   *
   * 此函数封装了使用OSS客户端进行文件上传的过程。它首先验证传入的参数是否有效，
   * 然后使用multipartUpload方法上传文件，并返回上传结果。
   *
   * @param {Object} client OSS客户端对象，必须实现multipartUpload方法。
   * @param {string} tempFilePath 要上传的文件临时路径。
   * @param {string} keyPrefix 上传文件在OSS中的前缀，用于组织文件。
   * @param {string} host OSS的主机名，用于构建文件的访问URL。
   * @param {string} fileName 文件名称。
   * @param {string} contentType 文件的Content-Type。
   * @param {Object} meta 文件的元数据。
   * @param {Object} headers 请求头对象，用于自定义请求头。
   * @param {Object} callback 设置上传成功回调参数，小程序上传暂不支持。
   * @param {Function} onProgressUpdate 上传进度回调函数。
   * @param {string} urlParams URL携带的参数。
   * @param {Object} options 其他可选参数。
   * @returns {Promise} 返回一个Promise对象，成功时解析为上传结果，失败时拒绝并抛出错误。
   */
	async uploadFileByOSS({
		client,
		tempFilePath,
		keyPrefix,
		host,
		fileName,
		contentType,
		meta,
		headers,
		callback,
		onProgressUpdate,
		urlParams = '',
		...options
	}) {
		// 验证client对象是否有效
		// 参数校验
		if (!client || typeof client.multipartUpload !== 'function') {
			return Promise.reject(new Error('Invalid client object'));
		}
		// 验证tempFilePath是否有效
		if (!tempFilePath || typeof tempFilePath !== 'string') {
			return Promise.reject(new Error('Invalid file string'));
		}
		// 验证keyPrefix和host是否为字符串
		if (typeof keyPrefix !== 'string' || typeof host !== 'string') {
			return Promise.reject(new Error('Invalid keyPrefix or host'));
		}
		let imageMaxSize = this.imageMaxSize;
		if (options && options.imageMaxSize) {
			imageMaxSize = options.imageMaxSize;
		}
		let file;
		try {
			file = await this.convertToFile(tempFilePath, fileName);
			console.log(file, 'old-file');
			console.log(isImageFile(file), 'isImageFile');
			// 调用convertToFile方法将file对象转换为Blob对象
			if (isImageFile(file) && file.size > imageMaxSize) {
				// 图片文件过大，压缩图片
				file = await this.compressImageByCompressor(file, options.compress || {});
				console.log(file, 'compressImageByCompressor-file');
			}
		} catch (error) {
			console.log('error:',error);
		 }
		console.log(file, 'filePath-file-file');
		return new Promise(async (resolve, reject) => {
			try {
				if (!file) {
					throw new Error('uploadFileByOSS-error,file获取失败');
				}
				let callbackOptions = {};
				// 设置回调参数
				if (callback && (contentType || Object.keys(callback).length !== 0)) {
					callbackOptions = {
						callback: {
							contentType,
							...callback
						}
					}; 
				}

				console.log(JSON.stringify(meta), '[uploadFileByOSS] meta'); 
				console.log(JSON.stringify(callbackOptions), '[uploadFileByOSS] callbackOptions'); 

				// 调用OSS客户端的multipartUpload方法上传文件
				const result = await client.multipartUpload(
					keyPrefix + file.name,
					file,
					{
						progress: (p) => {
							// 更新上传进度
							// console.log(p * 100, "上传进度");
							onProgressUpdate({
								progress: p * 100,
							});
						},
						partSize: 1024 * 1024, // 分片大小为1MB
						headers,
						meta,
						...callbackOptions,
						...options
					}
				);
				console.log(result);
				// 构建文件的访问URL
				let url = result.res.requestUrls[0].split('?')[0];

				// 解析上传结果，包含文件链接和其他相关信息
				resolve({
					code: 200,
					data: {
						link: `${url}${urlParams}`,
						attachId: null,
						domain: host,
						name: keyPrefix + file.name,
						originalName: file.name,
					},
					msg: '上传成功',
				});
			} catch (error) {
				console.error(error);
				// 抛出上传失败的错误
				// 增强的错误处理
				reject(new Error(`上传失败: ${error.message}`));
			}
		});
	}
	/**
   * 小程序使用的文件上传实现，通过uni.uploadFile API进行上传。
   * @param {string} tempFilePath 小程序中的文件对象。
   * @param {string} keyPrefix 文件在OSS中的前缀。
   * @param {Object} stsToken STS Token对象。
   * @param {string} fileName 文件名称。
   * @param {string} contentType 文件的Content-Type。
   * @param {Object} meta 文件的元数据。
   * @param {Object} headers 请求头对象，用于自定义请求头。
   * @param {Function} onProgressUpdate 上传进度回调函数。
   * @param {string} urlParams URL携带的参数。
   * @param {Object} options 其他可选参数。
   * @returns {Promise} 上传操作的Promise对象。
   */
	async uploadFileWx({
		tempFilePath,
		keyPrefix,
		stsToken,
		fileName,
		contentType,
		meta,
		headers,
		onProgressUpdate,
		retryCount = 0,
		urlParams = '',
		...options
	}) {
		// console.log(file, "file");
		// console.log(stsToken, "stsToken");
		// console.log(
		//   file.tempFilePath.split("/").at(-1),
		//   "file.tempFilePath.split('/').at(-1)"
		// );
		const host = `https://${stsToken.bucket}.${stsToken.region}.aliyuncs.com/`;
		// 文件路径及名称
	
		let metaObj = {}; // 自定义mata
		
		let imghead = await getImageInfo(fileName);
		if (imghead && Object.keys(imghead).length > 0) { 
			meta = imghead;
		} else {
			let urlInfo = await getPathInfo(tempFilePath); 
			if (urlInfo.imgId) {
				fileName = urlInfo.imgId;
				meta = urlInfo;
			}
		}

		// #ifdef MP-WEIXIN
		meta.source = 3;
		// #endif
 
		const key = keyPrefix + (fileName ? `${fileName}.${tempFilePath.split('.').at(-1)}` : tempFilePath.split('/').at(-1));
		let reslutUrl = `${host}${key}${urlParams}`;
		// console.log('xxxxxx1',fileName,imghead,reslutUrl); 
		// 特殊化meta数据
		Object.keys(meta).forEach(key => {
			// 转码，兼容中文字符
			const ossKey = key.replace(/_/g, '-');
			if (typeof meta[key] == 'string') {
				if (typeof meta[key] === 'string') {
					meta[key] = meta[key].replace(/&&/g, '_');
				}
			}
			metaObj[`x-oss-meta-${ossKey}`] = encodeURIComponent(meta[key]);
			// console.log('[oss-uploader.js] ossKey: ', meta[key],ossKey,metaObj[`x-oss-meta-${ossKey}`]);
		});

		const callback = {
			'callbackUrl':stsToken.callBackUrl,
			'callbackBody':JSON.stringify({...(meta || {}),url:reslutUrl}),
			callbackBodyType: 'application/x-www-form-urlencoded'
		};
		
		const formData = {
			fileName,
			key,
			policy: stsToken.policy,
			OSSAccessKeyId: stsToken.accessKeyId,
			signature: stsToken.signature,
			'callback':meta['model_id'] && meta['result'] !== undefined && meta['result'] !== '' ? base64.encode(JSON.stringify(callback)) : '',
			// success_action_status: "200",
			// securityToken: stsToken.securityToken,
			'x-oss-security-token': stsToken.securityToken || stsToken.stsToken, // 使用STS签名时必传
			'x-oss-content-type': contentType,
			...headers,
			...metaObj,
			...options
		};
		console.log('callback',callback,formData);
		// console.log(formData, metaObj,'meta:',meta, options,callback,'formData');
		// return;
		// 逻辑不变，但确保对上传结果的错误处理更加准确
		return new Promise((resolve, reject) => {
			uni.uploadFile({ 
				url: host,
				filePath: tempFilePath,
				name: 'file',
				timeout: 120000,
				formData,
				success:async (res) => {
					console.log(res, 'res-res-res');
					if (res.statusCode == 204 || res.statusCode == 200) {
						try { 
							uni.removeStorage({
								key: fileName
							});
							resolve({
								code: 200,
								data: {
									link:reslutUrl,
									attachId: null,
									domain: host,
									name: key,
									originalName: tempFilePath.split('/').at(-1)
								},
								msg: '上传成功',
							});
						} catch (parseError) {
							reject(parseError);
						}
					} else {
						reject(new Error(`Unexpected status code: ${res.statusCode}`));
					}
				},
				fail: async (err) => {
					if (err.statusCode === 403 && retryCount < 3) {
						// 如果是403错误，并且重试次数小于3次，尝试重新获取STS Token并重试
						console.log('遇到403错误，尝试重新获取STS Token');
						retryCount++; // 增加重试计数器

						// 获取新的STS Token
						let refreshSTSToken = await this.getStsToken();

						// 递归调用自身，进行重试
						await this.uploadFileWx(
							tempFilePath,
							keyPrefix,
							refreshSTSToken,
							fileName,
							onProgressUpdate,
							retryCount // 传递当前的重试次数
						);
					} else {
						// 如果不是403错误，或者重试次数已经达到上限，直接拒绝Promise
						console.error('Upload failed', err);
						reject(err);
					}
				},
			})
				.onProgressUpdate((res) => {
					// console.log(res, "onProgressUpdate-res");
					onProgressUpdate(res);
				});
		});
	}

	/**
   * 计算请求的签名。
   * 使用HMAC-SHA1算法和Base64编码方式，根据访问密钥 secret 和规范化的请求字符串计算签名。
   * 这个签名用于验证请求的发送者和确保请求的完整性。
   *
   * @param {String} accessKeySecret 访问密钥的秘钥部分，用于计算签名。
   * @param {String} canonicalString 规范化的请求字符串，包含了所有需要进行签名计算的请求参数。
   * @returns {String} 返回使用HMAC-SHA1算法计算出的签名的Base64编码。
   */
	computeSignature(accessKeySecret, canonicalString) {
		// 使用HMAC-SHA1算法和访问密钥 secret 对规范化的请求字符串进行签名计算
		// 然后将计算得到的签名结果使用Base64编码方式转换为字符串
		return crypto.enc.Base64.stringify(
			crypto.HmacSHA1(canonicalString, accessKeySecret)
		);
	}
	/**
   * 将给定的临时文件路径转换为Blob对象。
   * 使用fetch API从临时文件路径获取数据，然后将其转换为Blob对象。
   * 这样做的目的是为了在前端处理文件数据，例如上传到服务器。
   *
   * @param {string} tempFilePath - 临时文件路径，通常由相机或文件选择器提供。
   * @param {string} fileName 文件名称。
   * @returns {Promise<File>} 返回一个Promise，解析为一个File对象，代表转换后的Blob。
   */
	convertToFile(tempFilePath, fileName) {
		return new Promise(async (resolve, reject) => {
			try {
				// 判断tempFilePath是否为base64格式
				const isBase64 = (str) => {
					try {
						return btoa(atob(str)) === str;
					} catch (err) {
						return false;
					}
				};

				if (isBase64(tempFilePath)) {	
					// 如果是base64，将其转换为Blob对象
					const file = dataURLtoFile(tempFilePath, fileName);
					resolve(file);
				} else {
					// 使用fetch API从临时文件路径获取数据
					const respon = await fetch(tempFilePath);
					// 检查fetch请求是否成功
					if (!respon.ok) {
						throw new Error(`Fetch request failed with status ${respon.status}`);
					}
					// 将响应数据转换为Blob对象
					const blob = await respon.blob();
					
					// 定义视频文件的MIME类型映射
					const videoMimeTypes = {
						'mov': 'video/quicktime',
						'mp4': 'video/mp4',
						'avi': 'video/x-msvideo',
					};
					
					let extension;
					let mimeType;
					
					// 首先从原始路径获取实际文件扩展名
					const originalExtension = tempFilePath.split('.').pop().toLowerCase();
					
					// 判断实际文件类型
					if (videoMimeTypes[originalExtension]) {
						// 如果是视频文件，使用其实际扩展名而不是fileName中的扩展名
						extension = originalExtension;
						mimeType = videoMimeTypes[originalExtension];
					} else {
						// 非视频文件，使用blob的类型
						extension = blob.type.split('/')[1];
						if (extension === 'octet-stream') {
							extension = originalExtension;
							mimeType = videoMimeTypes[originalExtension] || blob.type;
						} else {
							mimeType = blob.type;
						}
					}

					if (extension.includes(';')) {
						extension = extension.split(';')[0];
					}

					let convertToFileName = '';
					// 如果提供了文件名但扩展名错误，使用正确的扩展名
					if (fileName) {
						// 提取文件名部分（不包含扩展名）
						const fileNameWithoutExt = fileName.includes('.') ? 
							fileName.substring(0, fileName.lastIndexOf('.')) : 
							fileName;
						// 使用正确的扩展名
						convertToFileName = `${fileNameWithoutExt}.${extension}`;
					} else {
					// 生成一个随机文件名，用于命名Blob对象
					// 生成随机文件名并保留正确的后缀
						convertToFileName = this.generateRandomFileName(extension);
					}

					// 使用正确的MIME类型创建文件
					const file = new File([blob], convertToFileName, { type: mimeType });
					console.log(`[convertToFile] convertToFileName ${convertToFileName},mimeType ${mimeType}`);
					resolve(file);
				}
			} catch (e) {
				console.error('Error converting to Blob:', e);
				reject('Error converting to Blob:' + e);
			}
		});
	}

	/**
   * 生成一个随机的文件名。
   * 这个函数用于为Blob对象生成一个唯一的、随机的文件名，以确保在存储或上传时不会发生名称冲突。
   * 文件名由当前时间戳和一段随机字符串组成，并以文件扩展名结尾。
   *
   * @param {string} extension - 文件的扩展名，用于指定生成文件名的后缀。
   * @returns {string} 返回一个随机生成的文件名，包含时间戳和随机字符串，以及指定的扩展名。
   */
	generateRandomFileName(extension) {
		// 生成一个随机字符串作为文件名的一部分
		const randomStr = Math.random().toString(36).substring(2, 10);
		// 生成一个当前时间戳作为文件名的一部分
		const timestamp = Date.now();
		// 结合后缀生成最终的文件名
		return `${timestamp}_${randomStr}.${extension}`;
	}
}

function dataURLtoFile(dataurl, filename) {
	let arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
		bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
	while (n--) {
		u8arr[n] = bstr.charCodeAt(n);
	}
	return new File([u8arr], filename, {type:mime});
}